---
id: event-bus
title: 22. 事件总线
sidebar_label: 22. 事件总线
---

import useBaseUrl from "@docusaurus/useBaseUrl";

## 22.1 什么是事件总线

事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制，允许不同的组件之间进行彼此通信而又不需要相互依赖，达到一种解耦的目的。

我们来看看事件总线的处理流程：

<img src={useBaseUrl("img/event1.png")} />

## 22.2 `MessageCenter` 消息中心

在 `Furion` 框架中，实现了一种轻量级的事件总线实现机制：`MessageCenter`（消息中心），`MessageCenter` 采用字符串消息机制进行广播， 可以在绝大多数中小型项目中发挥作用，缺点是消息处理是在主线程中完成并且消息不支持分布式存储。

另外，`MessageCenter` 支持单播、多播发布及多订阅。如图所示：

<img src={useBaseUrl("img/event2.png")} />

### 22.2.1 注册 `轻量级事件总线服务`

如果想使用 `MessageCenter` 轻量级事件总线，只需要在 `Startup.cs` 中注册服务即可，如：

```cs {3}
public void ConfigureServices(IServiceCollection services)
{
    services.AddSimpleEventBus();
}
```

### 22.2.2 定义订阅处理程序

`MessageCenter` 是根据 `MesseageId` 消息 Id 来触发对应的处理程序的，所以我们需要定义 `订阅处理程序类`，该类需实现 `ISubscribeHandler` 接口，如：

```cs {1,4-5,11-13}
public class UserChangeSubscribeHandler : ISubscribeHandler
{
    // 定义一条消息
    [SubscribeMessage("create:user")]
    public void CreateUser(string eventId, object payload)
    {
        Console.WriteLine("我是"+eventId);
    }

    // 多条消息共用同一个处理程序
    [SubscribeMessage("delete:user")]
    [SubscribeMessage("remove:user")]
    public void RemoveUser(string eventId, object payload)
    {
        Console.WriteLine("我是"+eventId);
    }

    // 支持异步
    [SubscribeMessage("delete:user")]
    public async Task SupportAsync(string eventId, object payload)
    {
        await MethodAsync();
    }
}
```

### 22.2.3 发布消息

定义好订阅处理程序后，我们就可以在程序任何地方进行广播消息，事件总线会自动根据 `消息 Id` 触发对应的处理程序方法：

```cs
MessageCenter.Send("create:user", new User {}); // => 打印：我是create:user

MessageCenter.Send("delete:user", new User {}); // => 打印：我是delete:user
MessageCenter.Send("remove:user", new User {}); // => 打印：我是remove:user
```

### 22.2.4 直接订阅消息

在上面的例子中，我们需要创建 `ISubscribeHandler` 的派生类进行相关配置才能实现订阅处理。

在某些特殊情况下，可能只需要订阅一次即可。所以，在 `Furion` 框架中，为了更简便的订阅消息，也提供了 `MessageCenter.Subscribe<T>` 静态方法，如：

```cs
MessageCenter.Subscribe<User>("create:user", (i,p) => {
    // do something。。。
});
```

## 22.3 同步方式执行

默认情况下，事件总线总是采用新线程方式执行，但是我们可以配置为同步方式，如：

```cs
MessageCenter.Send("create:user", isSync: true);
```

## 22.4 关于依赖注入

在 `Furion` 框架中，事件总线是不支持构造函数注入的，而且构造函数也只会执行一次。所以需要用到服务，应该通过静态类解析，`App.GetService<xx>()` 或 `Db.GetRepository<XX>()`。

```cs {5,12-20}
public class UserChangeSubscribeHandler : ISubscribeHandler
{
    public UserChangeSubscribeHandler()
    {
        // 不支持这里解析服务！！！！！！！！！！！
    }

    // 定义一条消息
    [SubscribeMessage("create:user")]
    public void CreateUser(string eventId, object payload)
    {
        // 创建一个作用域，对象使用完成自动释放
        Scoped.Create((_, scope) =>
        {
            var services = scope.ServiceProvider;

            var repository = Db.GetRepository<Person>(services);    // services 传递进去
            var someService = App.GetService<ISomeService>(services);   // services 传递进去
            var otherService = services.GetService<IOtherService>();    // 直接用 services 解析
        });
    }
}
```

:::note 关于 `App.GetService<TService>()` 解析服务

在高频定时任务中调用`App.GetService(TService)`，可能会出现内存无法回收的情况，建议始终使用`scope.ServiceProvider.GetService(TService)`来获取`TService`

:::

:::important 数据库操作注意

如果作用域中对**数据库有任何变更操作**，需手动调用 `SaveChanges` 或带 `Now` 结尾的方法。也可以使用 `Scoped.CreateUow(handler)` 代替。

:::

:::warning 关于依赖注入

`ISubscribeHandler` 接口主要是用来查找定义事件对象的，也就是它的实现类并未提供依赖注入功能，所以在实现类并不支持构造函数注入依赖项。

:::

## 22.5 反馈与建议

:::note 与我们交流

给 Furion 提 [Issue](https://gitee.com/dotnetchina/Furion/issues/new?issue)。

:::
